﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace WorldOnFire
{
    public abstract class ABCTurret : ABCObject
    {
        public enum eTurretType
        {
            REPEATER,
            FLAK,
            CANNON,
            LASER,
            EMP,
            GRAVITY_WELL,
            BLACK_HOLE,
            RELAY,
            COUNT
        };

        // Turret data
        private eTurretType m_eTurretType;
        private ABCProjectile.eProjectileType m_eProjectileType;

        // Rendering data
        private CRenderingManager.eTextureID m_eIlluminationTextureID;

        protected void SetTurretType(eTurretType _eTurretType)
        { m_eTurretType = _eTurretType; }

        public eTurretType GetTurretType()
        { return m_eTurretType; }

        public ABCEnemy.eEnemyType[] GetPrefferedEnemies()
        { return m_eEnemyTargetingPreference; }

        protected void SetProjectileType(ABCProjectile.eProjectileType _eProjectileType)
        { m_eProjectileType = _eProjectileType; }

        public ABCProjectile.eProjectileType GetProjectileType()
        { return m_eProjectileType; }

        protected float[] m_fRange;
        protected float[] m_fShotDelay;
        protected float[] m_fShotSpread;
        protected float[] m_fShotSpeed;
        protected float[] m_fDamage;
        protected float[] m_fAreaOfEffect;

        // TODO: Targeting preferences
        protected ABCEnemy.eEnemyType[] m_eEnemyTargetingPreference;

        // Targeting data
        protected ABCEnemy.eEnemyType[] m_eTargetType;
        protected int[] m_nTargetIndex;
        protected bool[] m_bTargetAcquired;

        // Firing data
        protected float[] m_fShotTimer;
        protected float[] m_fTargetingTimer;

        // Orbit data
        protected Vector3[] m_vOrbitCenter;
        protected Vector3[] m_vOrbitAxis;
        protected float[] m_fOrbitRotation;
        protected float[] m_fOrbitRotationRate;
        protected float[] m_fOrbitRadius;

        // TODO: Something else.
        protected float[] m_fPitch;
        protected float[] m_fRoll;
        protected float[] m_fPitchSpeed;
        protected float[] m_fRollSpeed;

        // Visual data
        protected float m_fTurretOffset;
        
        // Constant data
        protected const float fROTATION_SPEED = (float)Math.PI;

        // Accessors
        public bool HasTarget(int _nIndex)
        { return m_bTargetAcquired[_nIndex]; }

        public float GetRange(int _nIndex)
        { return m_fRange[_nIndex]; }

        public float GetDamage(int _nIndex)
        { return m_fDamage[_nIndex]; }

        public float GetAreaOfEffect(int _nIndex)
        { return m_fAreaOfEffect[_nIndex]; }

        public float GetShotSpeed(int _nIndex)
        { return m_fShotSpeed[_nIndex]; }

        public float GetTurretOffset()
        { return m_fTurretOffset; }

        public ABCEnemy.eEnemyType GetTargetType(int _nIndex)
        { return m_eTargetType[_nIndex]; }

        public int GetTargetIndex(int _nIndex)
        { return m_nTargetIndex[_nIndex]; }

        public void SetTarget(int _nIndex, ABCEnemy.eEnemyType _eEnemyType, int _nEnemyIndex)
        {
            m_bTargetAcquired[_nIndex] = true;
            m_eTargetType[_nIndex] = _eEnemyType;
            m_nTargetIndex[_nIndex] = _nEnemyIndex;
        }

        public void SetCenterOfOrbit(int _nIndex, Vector3 _vCenterOfOrbit)
        { m_vOrbitCenter[_nIndex] = _vCenterOfOrbit; }

        public ABCTurret()
        {

        }

        public int CreateTurret(CBuildable _Buildable)
        {
            // If there is an unused projectile
            if (this.FindUnusedIndex() == true)
            {
                this.InitializeTurret(m_nInactiveObjectIndex, _Buildable);

                return m_nInactiveObjectIndex;
            }

            return -1;
        }

        protected virtual void InitializeTurret(int _nTurretIndex, CBuildable _Buildable)
        {
            // Set the object to created
            this.CreateObject(_nTurretIndex);

            // Pick a random axis to orbit around
            m_vOrbitAxis[_nTurretIndex] = new Vector3(s_Ron.NextFloat(-1.0f, 1.0f),
                s_Ron.NextFloat(-1.0f, 1.0f), 0.0f);
            m_vOrbitAxis[_nTurretIndex].Normalize();

            // Pick a random initial rotation and rotation speed
            m_fOrbitRotation[_nTurretIndex] = s_Ron.NextFloat(0.0f, 2.0f * (float)Math.PI);
            m_fOrbitRotationRate[_nTurretIndex] = 
                s_Ron.NextFloat(0.1f * (float)Math.PI, 0.15f * (float)Math.PI);

            // Set the orbit radius
            m_fOrbitRadius[_nTurretIndex] = _Buildable.GetOrbitRadius();
        }

        public override void Initialize(int _nObjectCount)
        {
            // Initialize the arrays
            m_fRange = new float[_nObjectCount];
            m_fShotDelay = new float[_nObjectCount];
            m_fShotSpread = new float[_nObjectCount];
            m_fShotSpeed = new float[_nObjectCount];
            m_fDamage = new float[_nObjectCount];
            m_fAreaOfEffect = new float[_nObjectCount];

            m_eTargetType = new ABCEnemy.eEnemyType[_nObjectCount];
            m_nTargetIndex = new int[_nObjectCount];
            m_bTargetAcquired = new bool[_nObjectCount];

            m_fShotTimer = new float[_nObjectCount];
            m_fTargetingTimer = new float[_nObjectCount];

            m_vOrbitCenter = new Vector3[_nObjectCount];
            m_vOrbitAxis = new Vector3[_nObjectCount];
            m_fOrbitRotation = new float[_nObjectCount];
            m_fOrbitRotationRate = new float[_nObjectCount];
            m_fOrbitRadius = new float[_nObjectCount];

            // TODO: HURRRR
            m_fPitch = new float[_nObjectCount];
            m_fRoll = new float[_nObjectCount];
            m_fPitchSpeed = new float[_nObjectCount];
            m_fRollSpeed = new float[_nObjectCount];

            // Initialize the base members
            base.Initialize(_nObjectCount);

            // Set rendering data
            m_nTechniqueID = (int)CRenderingManager.eObjectTechnique.ILLUMINATION;
            m_bOverlay = true;

            // Set the team enumeration
            for (int iTurret = 0; iTurret < _nObjectCount; ++iTurret)
            {
                this.SetTeam(iTurret, eTeam.PLAYER);

                // TODO: KILL
                m_fPitchSpeed[iTurret] = s_Ron.NextFloat(-0.1f * (float)Math.PI, 0.1f * (float)Math.PI);
                m_fRollSpeed[iTurret] = s_Ron.NextFloat(-0.1f * (float)Math.PI, 0.1f * (float)Math.PI);
            }
        }

        public override void LoadContent()
        {
            // Load the turret texture and illumination map
            m_eTextureID = CRenderingManager.eTextureID.TURRET;
            m_eIlluminationTextureID = CRenderingManager.eTextureID.TURRET_ILLUMINATION;

            // Load base content
            base.LoadContent();
        }

        public override void Update(float _fDeltaTime)
        {
            // Loop through all of the turrets for orbit
            for (int iTurret = 0; iTurret < m_nObjectCount; ++iTurret)
            {
                // If the turret is inactive
                if (m_bObjectActive[iTurret] == false)
                {
                    // Skip this turret
                    continue;
                }

                // Increment the orbit rotation
                m_fOrbitRotation[iTurret] += m_fOrbitRotationRate[iTurret] * _fDeltaTime;

                // Determine the position in orbit
                // (Position = Orbit Distance * Orbit Rotation)
                Matrix mOrbitOffset = Matrix.CreateTranslation(new Vector3(0.0f, 0.0f, m_fOrbitRadius[iTurret]));
                Matrix mOrbitRotation = Matrix.CreateFromAxisAngle(m_vOrbitAxis[iTurret], m_fOrbitRotation[iTurret]);

                m_fPitch[iTurret] += m_fPitchSpeed[iTurret] * _fDeltaTime;
                m_fRoll[iTurret] += m_fRollSpeed[iTurret] * _fDeltaTime;

                Matrix mSpinRotation = Matrix.CreateFromYawPitchRoll(0.0f, m_fPitch[iTurret], m_fRoll[iTurret]);
                //m_mTransform[iTurret] = mSpinRotation;
                m_mTransform[iTurret].Translation = (mOrbitOffset * mOrbitRotation).Translation
                    + m_vOrbitCenter[iTurret];
            }

            // Loop through all the turrets for attacking
            for (int iTurret = 0; iTurret < m_nObjectCount; ++iTurret)
            {
                // If the turret is inactive
                if (m_bObjectActive[iTurret] == false)
                {
                    // Skip this turret
                    continue;
                }

                // If there is a target to attack
                if (m_bTargetAcquired[iTurret] == true)
                {
                    // Kill, kill, kill for it
                    this.Attack(iTurret, _fDeltaTime);
                }
            }

            // Update the base stuff
            base.Update(_fDeltaTime);
        }

        public override void Render(Microsoft.Xna.Framework.Graphics.GraphicsDevice _GraphicsDevice)
        {
            Effect TurretEffect = s_RenderingManager.GetEffect(m_eShaderID);
            TurretEffect.Parameters["g_IlluminationMap"].SetValue(s_RenderingManager.GetTexture(m_eIlluminationTextureID));

            base.Render(_GraphicsDevice);
        }

        public void Attack(int _nIndex, float _fDeltaTime)
        {
            // Decrement the time until targeting data needs re-verification
            m_fTargetingTimer[_nIndex] -= _fDeltaTime;

            // If it is time to re-verify targeting data
            if (m_fTargetingTimer[_nIndex] < 0.0f)
            {
                //m_fTargetingTimer[_nIndex] = 2.0f;

                ABCEnemy Enemy = s_ObjectManager.GetEnemy(m_eTargetType[_nIndex]);
                Vector3 vTargetPosition 
                    = Enemy.GetPosition(m_nTargetIndex[_nIndex]);

                // If the enemy has been destroyed
                if (Enemy.IsObjectActive(m_nTargetIndex[_nIndex]) == false)
                {
                    // Set the target to unacquired
                    m_bTargetAcquired[_nIndex] = false;

                    // Bail out
                    return;
                }

                // Find the between vector and distance
                Vector3 vBetween = vTargetPosition - m_mTransform[_nIndex].Translation;
                float fDistance = vBetween.Length();

                // If the enemy is too far away
                if (fDistance > m_fRange[_nIndex])
                {
                    // Set the target to unacquired
                    m_bTargetAcquired[_nIndex] = false;
                   
                    // Bail out
                    return;
                }

                Vector3 vProjectedPosition = Enemy.GetProjectedPosition(m_nTargetIndex[_nIndex], fDistance / m_fShotSpeed[_nIndex]);
                vBetween = vProjectedPosition - m_mTransform[_nIndex].Translation;
                vBetween.Normalize();

                // Normalize the between vector
                //vBetween /= fDistance;

                // If the turret isn't facing the right way or its behind its satellite
                if (Vector3.Dot(vBetween, m_mTransform[_nIndex].Backward) < 0.9f)
                {
                    // Don't start shooting yet
                    m_fShotTimer[_nIndex] = 0.0f;
                }

                // Aim towards the target
                this.TurnTowards(_nIndex, vProjectedPosition, _fDeltaTime * fROTATION_SPEED, true);
            }

            // Increment the shot timer
            m_fShotTimer[_nIndex] += _fDeltaTime;

            // If it's time to fire and the turret isn't behind the celestial body
            if (m_fShotTimer[_nIndex] > m_fShotDelay[_nIndex])
            {
                // Calculate the direction to the celestial body
                Vector3 vCelestialBodyDirection = Vector3.Normalize(
                    m_vOrbitCenter[_nIndex] - m_mTransform[_nIndex].Translation);

                // If the celestial body is not blocking
                if (Vector3.Dot(m_mTransform[_nIndex].Backward, vCelestialBodyDirection) < 0.85f)
                {
                    // Reset the shot timer
                    m_fShotTimer[_nIndex] = 0.0f;

                    // Kill kill kill
                    this.Fire(_nIndex);
                }
            }
        }

        public virtual void Fire(int _nIndex)
        {
            s_ObjectManager.GetProjectile(m_eProjectileType).CreateProjectile(this, _nIndex, new Vector3(0.0f, 0.0f, 0.0f));
        }
    }
}
